package com.nc.mem.serial.graph.common;

import static com.nc.mem.serial.graph.Representation.idToClass;
import static com.nc.mem.serial.graph.Representation.readInt;
import static com.nc.mem.serial.graph.Representation.readLong;
import static com.nc.mem.serial.graph.Representation.writeClassId;

import java.nio.ByteBuffer;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;

import com.nc.mem.serial.Serializer;
import com.nc.mem.serial.graph.Context;
import com.nc.mem.serial.graph.GraphSerializer;
import com.nc.mem.serial.graph.Representation;
import com.nc.mem.serial.graph.asm.GraphSerializerFactory;
import com.nc.mem.serial.graph.container.CollectionRegistry;


public class MapSerializer extends GraphSerializer {

	public static MapSerializer INSTANCE = new MapSerializer();

	private MapSerializer() {
	}

	@SuppressWarnings("unchecked")
	@Override
	public void doRead(final ByteBuffer src, final Object obj) {
		final Context current = Context.current();
		final Map<Object, Object> map = (Map<Object, Object>) obj;
		final int sz = (int) (readLong(src) >> 1);

		if (sz > 0) {
			int s;
			/*
			 * Cached inline: most maps won't actually use polymorphic references, so we only need
			 * to perform a single lookup on the serializers dictionary.
			 */
			Class<?> kClazz = null;
			Class<?> vClazz = null;
			Serializer kSer = null;
			Serializer vSer = null;
			Serializer kCurr;
			Serializer vCurr;
			Object key;
			Object val;
			for (int i = 0; i < sz; i++) {
				s = Representation.readInt(src);
				if (s == 1) {
					key = null;
				} else if ((s & 1 << 1) != 0) {
					key = current.fromId(s);
				} else {
					final Class<?> currClazz = Representation.idToClass(src);
					if (kClazz == null) {
						kClazz = currClazz;
						kSer = GraphSerializerFactory.serializer(currClazz);
					}

					kCurr = currClazz == kClazz ? kSer : GraphSerializerFactory.serializer(currClazz);

					key = kCurr.read(src, s);
				}

				s = readInt(src);
				if (s == 1) {
					val = null;
				} else if ((s & 1 << 1) != 0) {
					val = current.fromId(s);
				} else {
					final Class<?> currClazz = idToClass(src);
					if (vClazz == null) {
						vClazz = currClazz;
						vSer = GraphSerializerFactory.serializer(currClazz);
					}

					vCurr = currClazz == vClazz ? vSer : GraphSerializerFactory.serializer(currClazz);

					val = vCurr.read(src, s);
				}
				map.put(key, val);
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void doWrite(final ByteBuffer dest, final Object obj) {
		final Map<Object, Object> objs = (Map<Object, Object>) obj;
		final int size = objs.size();
		writeClassId(dest, objs.getClass());

		final Comparator<?> c = objs instanceof SortedMap ? ((SortedMap<?, ?>) objs).comparator() : null;
		final long s = objs.size() << 1 | (c != null ? 1 : 0);

		Representation.writeLong(dest, s);

		if (c != null) {
			GraphSerializerFactory.serializer(c.getClass()).write(dest, c);
			Representation.writeLong(dest, s);
		}

		if (size > 0) {
			/*
			 * Cached inline: most maps won't actually use polymorphic references, so we only need
			 * to perform a single lookup on the serializers dictionary.
			 */
			Class<?> kClazz = null;
			Class<?> vClazz = null;
			Serializer kSer = null;
			Serializer vSer = null;
			Serializer kCurr;
			Serializer vCurr;
			final Set<Entry<Object, Object>> entries = objs.entrySet();
			for (final Entry<Object, Object> entry : entries) {
				final Object key = entry.getKey();
				final Object val = entry.getValue();
				if (key == null) {
					Representation.writeInt(dest, 1);
				} else {
					if (kClazz == null) {
						kClazz = key.getClass();
						kSer = GraphSerializerFactory.serializer(kClazz);
					}
					kCurr = key.getClass() == kClazz ? kSer : GraphSerializerFactory.serializer(key.getClass());
					kCurr.write(dest, key);
				}

				if (val == null) {
					Representation.writeInt(dest, 1);
				} else {
					if (vClazz == null) {
						vClazz = val.getClass();
						vSer = GraphSerializerFactory.serializer(vClazz);
					}
					vCurr = val.getClass() == vClazz ? vSer : GraphSerializerFactory.serializer(val.getClass());
					vCurr.write(dest, val);
				}
			}
		}
	}

	@Override
	public Object instantiate(final ByteBuffer src) {
		final int id = readInt(src);
		final int p = src.position();
		final long szAndKind = readLong(src);

		Object ret;

		if ((szAndKind & 1) != 0) {
			final int s = readInt(src);

			Comparator<?> c = (Comparator<?>) Context.current().fromId(s);

			if (c == null) {
				c = (Comparator<?>) GraphSerializerFactory.serializer(idToClass(src)).read(src, s);
			}

			ret = CollectionRegistry.INSTANCE.newMap(id, c);
		} else {
			src.position(p);
			ret = CollectionRegistry.INSTANCE.newMap(id, (int) (szAndKind >> 1));
		}

		return ret;
	}
}
